next up previous
Next: 3.5 Using inttypes.h Up: 3 Generic 64-bit Portability Previous: 3.3 Poorly Sized Malloc

3.4 Careful Using Variable Argument Lists

  
Table 2: Explanation of typedefs in inttypes.h.

Routines that take variable argument lists can often hide 64-bit portability problems. The most common examples of such routines are standard routines like print, scanf, fprintf, syslog, vprintf, and their relatives. In the case of printf-like routines, the variable number of arguments of various types are interpreted at run-time by a format string parameter. Routines using variable argument lists tend to obscure 64-bit portability problems for two related reasons:

  1. The compiler cannot warn you of argument passing type mismatches.
  2. The compiler may not promote parameters to expected types.
Here is a non-portable program demonstrating the first problem:
#include <stdio.h>
main(int argc, char **argv)
{
   char *thirty = "30";
   long value;
   sscanf(thirty, "%d", &value);
   printf("result = %d\n", value);
}
On a 32-bit system, the program outputs 30 as expected. On a 64-bit LP64 system, the program outputs an undefined value. The reason is because value is declared as a long but is printed out by the %d format specifier intended for int values. The program can be fixed by either changing the type of value to int or by changing the sscanf format string to be %ld to scan into a long value.

More 64-bit printf-related advice is to use the %p format specifier (defined in the ANSI C library specification) when printing pointers values (generally, this is limited to debugging purposes). Using the more traditional %x specifier to print pointers as hexadecimal integers will output a truncated representation of 64-bit pointers. Bogus debugging output can only make debugging your 64-bit code harder.

To demonstrate the second problem, consider a routine to print a null terminated list of character strings. Remember that a null pointer is represented by the integral value 0. The routine is implemented portably as:

#include <stdarg.h>
void print_list(char *word, ...)
{
   va_list ap;

   va_start(ap, word);
   while(word) {
      puts(word);
      word = va_arg(ap, char *);
   }
   va_end(ap);
}
But, problems can result when you call the routine in an unportable manner. For example:
  print_list("hi", "there", 0);
Trailing variable arguments beyond the explictly typed arguments of print_list suffer the default argument promotion rules. It is ambiguous whether the trailing zero should be passed as a pointer or an integer. If treated as an integer, default argument promotion rules would pass the 0 as an int which is not the size of a 64-bit LP64 pointer. The call to print_list could be portably rewritten:
  print_list("hi", "there", (void*) 0);
Using the standard NULL symbolic constant found in stdio.h or stdlib.h should also ensure portability.



next up previous
Next: 3.5 Using inttypes.h Up: 3 Generic 64-bit Portability Previous: 3.3 Poorly Sized Malloc



Mark Kilgard
Sat Dec 30 11:52:07 PST 1995